package com.enba.boot.security.filter;

import com.alibaba.fastjson.TypeReference;
import com.enba.boot.core.base.Result;
import com.enba.boot.redis.RedisOperator;
import com.enba.boot.security.entity.IUser;
import com.enba.boot.security.enums.AuthStatusEnum;
import com.enba.boot.security.jwt.JwtTokenUtils;
import com.enba.boot.security.jwt.JwtUserDetails;
import com.enba.boot.security.properties.SecurityJwtProperties;
import com.enba.boot.security.properties.SecurityRedisKeyProperties;
import com.enba.boot.security.properties.SecurityUrlProperties;
import io.jsonwebtoken.Claims;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

@Slf4j
@Component
public class TokenFilter extends OncePerRequestFilter {

  private final JwtTokenUtils jwtTokenUtils;
  private final SecurityJwtProperties jwtProperties;
  private final SecurityUrlProperties securityUrlProperties;
  private final RedisOperator redisOperator;
  private final SecurityRedisKeyProperties securityRedisKeyProperties;

  public TokenFilter(
      JwtTokenUtils jwtTokenUtils,
      SecurityJwtProperties jwtProperties,
      SecurityUrlProperties securityUrlProperties,
      RedisOperator redisOperator,
      SecurityRedisKeyProperties securityRedisKeyProperties) {
    this.jwtTokenUtils = jwtTokenUtils;
    this.jwtProperties = jwtProperties;
    this.securityUrlProperties = securityUrlProperties;
    this.redisOperator = redisOperator;
    this.securityRedisKeyProperties = securityRedisKeyProperties;
  }

  @Override
  protected void doFilterInternal(
      HttpServletRequest request, HttpServletResponse response, FilterChain chain)
      throws ServletException, IOException {
    // 如果是登陆接口，走登陆逻辑
    if (request.getRequestURI().equals(securityUrlProperties.getLoginUrl())) {
      chain.doFilter(request, response);
    } else {
      String tokenHeader = request.getHeader(jwtProperties.getTokenHeader());
      if (StringUtils.hasText(tokenHeader)
          && tokenHeader.startsWith(jwtProperties.getTokenPrefix())) {
        // 获取token
        String authToken = tokenHeader.replace(jwtProperties.getTokenPrefix() + " ", "");

        // 解析token
        Claims claims;
        try {
          claims = jwtTokenUtils.parseToken(authToken);
        } catch (Exception e) {
          log.error("令牌非法：{}", e.getMessage(), e);
          Result.render(
              response,
              Result.err(
                  AuthStatusEnum.TOKEN_ILLEGAL.getCode(), AuthStatusEnum.TOKEN_ILLEGAL.getMsg()));
          return;
        }

        // 检查redis中是否存在此token且是否需要续期（距离过期时间X分钟时重置过期时间）
        if (Objects.nonNull(claims)
            && SecurityContextHolder.getContext().getAuthentication() == null) {

          String userKey = claims.get("userKey", String.class);

          // 获取用户信息
          IUser user =
              redisOperator.get(securityRedisKeyProperties.getUserKey() + userKey, IUser.class);
          if (user == null) {
            Result.render(
                response,
                Result.err(
                    AuthStatusEnum.AUTHENTICATION_ENTRY_POINT.getCode(),
                    AuthStatusEnum.AUTHENTICATION_ENTRY_POINT.getMsg()));
            return;
          }

          // 获取角色信息
          List<String> roleList =
              redisOperator.get(
                  securityRedisKeyProperties.getRoleKey() + userKey,
                  new TypeReference<List<String>>() {});

          // 获取权限信息
          List<String> permissionList =
              redisOperator.get(
                  securityRedisKeyProperties.getPermissionKey() + userKey,
                  new TypeReference<List<String>>() {});
          UserDetails userDetails = new JwtUserDetails(user, roleList, permissionList);

          UsernamePasswordAuthenticationToken authentication =
              new UsernamePasswordAuthenticationToken(
                  userDetails, null, userDetails.getAuthorities());
          authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
          // 将用户信息保存到ThreadLocal中
          SecurityContextHolder.getContext().setAuthentication(authentication);
        }
      }
      chain.doFilter(request, response);
    }
  }
}
